package myJs.user.workflow.featureSelection

import cats.effect.SyncIO
import myJs.I18n
import myJs.Implicits._
import myJs.Utils._
import myJs.myPkg._
import myJs.myPkg.jquery._
import myJs.myPkg.topology._
import myJs.tool.Tool
import myJs.tool.Tool._
import myJs.user.FeatureSelectionTool
import myJs.user.workflow.featureSelection.cor.Cor
import myJs.user.workflow.featureSelection.diagnoseAna.DiagnoseAna
import myJs.user.workflow.featureSelection.intersect.Intersect
import myJs.user.workflow.featureSelection.pathwayAna.PathwayAna
import myJs.user.workflow.featureSelection.pca.Pca
import myJs.user.workflow.featureSelection.uniAna.UniAna
import org.scalajs.dom._
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits._
import shared.SharedTool
import shared.pojo.WorkflowMission.FsData

import scala.scalajs.js
import scala.scalajs.js.Dynamic.{global => g}
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.JSON
import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel}
import com.softwaremill.quicklens._
import myJs.myPkg.reactHookForm.UseFieldArrayRT
import myJs.user.workflow.featureSelection.boruta.Boruta
import myJs.user.workflow.featureSelection.oplsda.Oplsda
import myJs.user.workflow.featureSelection.plsda.Plsda
import myJs.user.workflow.featureSelection.rf.Rf
import myJs.user.workflow.featureSelection.svm.Svm
import myJs.user.workflow.featureSelection.union.Union

import scala.collection.immutable.SeqMap

/** Created by yz on 2019/4/25
  */
@JSExportTopLevel("FeatureSelection")
object FeatureSelection extends FeatureSelectionTool {

  var beforeRightWidth: Double = _
  var beforeLeftWidth: Double = _

  @JSExport("init")
  def init = {
    I18n.initialize()
    Cp.Props(missionName = getMissionName).render.renderIntoDOM(document.querySelector("#root"))

    registerEvent

    val leftResizeOptions = ResizableOptions
      .resizeWidth(true)
      .resizeHeight(false)
      .handleSelector(".leftHandler,.leftDrag")
      .onDrag(leftDrag)
      .resizeWidthFrom("right")
    $("#left").resizable(leftResizeOptions)
    Tool.rightResizable

  }

  def getMissionName = {
    g.missionName.toString
  }

  def getId = {
    g.id.toString.toInt
  }

  def registerEvent = {
    $(document.body).on(
      "click",
      s"#left .panel-collapse",
      "",
      (y: Element) => {
        val parentJq = $("#left")
        val curIsVisible = parentJq.find(".unified-panel-header").isVisible
        val rowWidth = parentJq.parents(".row").css("width").px2Double
        val curWidth = $("#center").css("width").px2Double
        val curPercent = (curWidth / rowWidth) * 100
        val foldWidth = 2
        if (curIsVisible) {
          beforeLeftWidth = (parentJq.width() / rowWidth) * 100
          val changeSize = beforeLeftWidth - foldWidth
          parentJq.css("width", s"${foldWidth}%")
          $("#center").css("width", s"${curPercent + changeSize}%")
          parentJq.find(".unified-panel-header,.unified-panel-body").hide()
          parentJq.find(".panel-collapse").css("border-right", "inherit")
        } else {
          val changeSize = beforeLeftWidth - foldWidth
          parentJq.css("width", s"${beforeLeftWidth}%")
          $("#center").css("width", s"${curPercent - changeSize}%")
          parentJq.find(".unified-panel-header,.unified-panel-body").show()
          parentJq.find(".panel-collapse").css("border-right", "solid #ddd 1px")
        }
        parentJq
          .find(".panel-collapse span")
          .toggleClass("fa-chevron-left fa-chevron-right")
      }
    )
    $(document.body).on(
      "click",
      s"#right .panel-collapse",
      "",
      (y: Element) => {
        val parentJq = $("#right")
        val curIsVisible = parentJq.find(".unified-panel-header").isVisible
        val rowWidth = parentJq.parents(".row").css("width").px2Double
        val curWidth = $("#center").css("width").px2Double
        val curPercent = (curWidth / rowWidth) * 100
        val foldWidth = 2
        if (curIsVisible) {
          beforeRightWidth = ($("#right").width() / rowWidth) * 100
          val changeSize = beforeRightWidth - foldWidth
          parentJq.css("width", s"${foldWidth}%")
          $("#center").css("width", s"${curPercent + changeSize}%")
          parentJq.find(".unified-panel-header,.unified-panel-body").hide()
          parentJq.find(".panel-collapse").css("border-left", "inherit")
        } else {
          val changeSize = beforeRightWidth - foldWidth
          parentJq.css("width", s"${beforeRightWidth}%")
          $("#center").css("width", s"${curPercent - changeSize}%")
          parentJq.find(".unified-panel-header,.unified-panel-body").show()
          parentJq.find(".panel-collapse").css("border-left", "solid #ddd 1px")
        }
        parentJq
          .find(".panel-collapse span")
          .toggleClass("fa-chevron-left fa-chevron-right")
      }
    )
  }

  def otherNodeHasMoreInput(idStr: String, topology: Topology) = {
    val graph = getGraph(topology)
    val curNode = graph.get(idStr)
    curNode.incoming.size > 1
  }

  def isOtherNode(idStr: String) = {
    !isInterUnionNode(idStr)
  }

  def hasCycle(topology: Topology) = {
    val graph = getGraph(topology)
    val hasCycleOp = graph.findCycle
    hasCycleOp.isDefined
  }

  def getNewNodeId(nodes: List[TopologyNode]) = {
    val kind = SharedTool.getKind(nodes.head.id)
    val curIds = nodes.map { node =>
      node.id.replaceAll(s"${kind}" + "_", "").toInt
    }
    curIds.max + 1
  }

  def getCopyNewNode(originalNode: TopologyNode, id: Int) = {
    val kind = SharedTool.getKind(originalNode.id)
    val onlyUpKinds =
      List(PathwayAna.nameStr, DiagnoseAna.nameStr, Pca.nameStr)
    val anchorType = if (onlyUpKinds.contains(kind)) {
      AnchorType.OnlyUp
    } else AnchorType.UpAndBottom
    val idSuffix = if (id == 0) "" else s"_${id}"
    val radiusKinds = List(PathwayAna, DiagnoseAna, Pca).map(_.nameStr)
    val borderRadius = if (radiusKinds.contains(kind)) 0.8 else 0.1
    val height = if (Tool.isEn) {
      kind match {
        case UniAna.nameStr => 40
        case Cor.nameStr => 50
        case Intersect.nameStr => 40
        case PathwayAna.nameStr => 40
        case DiagnoseAna.nameStr => 40
        case _ => 30
      }
    } else 30
    TopologyNode(
      id = s"${kind}_${id}",
      name = "rectangle",
      borderRadius = borderRadius,
      paddingBottom = 10,
      paddingTop = 10,
      paddingLeft = 10,
      paddingRight = 10,
      text = s"${LeftModulesCp.moduleMap(kind)}${idSuffix}",
      rect = Rect(
        width = 100,
        height = height,
        x = originalNode.rect.x + 20,
        y = originalNode.rect.y + 20,
        ex = originalNode.rect.ex + 20,
        ey = originalNode.rect.ey + 20
      ),
      anchorType = anchorType
    )
  }

  def getCurI(nodes: List[TopologyNode], node: TopologyNode) = {
    val idCurIMap = nodes
      .map(_.id)
      .map { id =>
        val index = id.replaceAll(s"^[^_]+_", "").toInt
        (id, index)
      }
      .sortBy(_._2)
      .zipWithIndex
      .map { case ((id, index), curI) =>
        (id, curI)
      }
      .toMap
    idCurIMap(node.id)
  }

  def appendCopyData(
      fsData: FsData,
      curI: Int,
      nodeId: String,
      fsDataChange: FsData => SyncIO[Unit],
      fieldArray: UseFieldArrayRT
  ) = {
    val kind = SharedTool.getKind(nodeId)
    kind match {
      case Pca.nameStr =>
        val newEachData = fsData.pca(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.pca).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case DiagnoseAna.nameStr =>
        val newEachData = fsData.diagnoseAna(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.diagnoseAna).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case PathwayAna.nameStr =>
        val newEachData = fsData.pathwayAna(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.pathwayAna).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Union.nameStr =>
        val newEachData = fsData.union(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.union).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Intersect.nameStr =>
        val newEachData = fsData.intersect(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.intersect).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Cor.nameStr =>
        val newEachData = fsData.cor(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.cor).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Boruta.nameStr =>
        val newEachData = fsData.boruta(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.boruta).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Svm.nameStr =>
        val newEachData = fsData.svm(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.svm).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Rf.nameStr =>
        val newEachData = fsData.rf(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.rf).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Plsda.nameStr =>
        val newEachData = fsData.plsda(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.plsda).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case Oplsda.nameStr =>
        val newEachData = fsData.oplsda(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.oplsda).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
      case UniAna.nameStr =>
        val newEachData = fsData.uniAna(curI).modify(_.nodeId).setTo(nodeId)
        val newFsData = fsData.modify(_.uniAna).using { x =>
          x :+ newEachData
        }
        fieldArray.append(newEachData)
        fsDataChange(newFsData)
    }
  }

  def batchDeleteNodes(
      nodes: List[TopologyNode],
      idCurIMap: SeqMap[String, Int],
      fsData: FsData,
      fsDataChange: (FsData => FsData) => SyncIO[Unit],
      fieldArrayMap: SeqMap[String, UseFieldArrayRT]
  ) = {
    val kindCurIs = nodes
      .map { node =>
        val kind = SharedTool.getKind(node.id)
        (kind, node)
      }
      .groupMap(_._1)(_._2)
      .map { case (kind, curNodes) =>
        val curIs = curNodes.map(_.id).map(idCurIMap(_))
        (kind, curIs)
      }
      .toList

    def loop(modifyF: FsData => FsData, kindCurIs: List[(String, List[Int])]): FsData => FsData = {
      kindCurIs match {
        case x :: xs =>
          val (kind, curIs) = x
          val newModifyF = kind match {
            case Pca.nameStr =>
              modify(_: FsData)(_.pca).using { x =>
                x.removed(curIs)
              }
            case DiagnoseAna.nameStr =>
              modify(_: FsData)(_.diagnoseAna).using { x =>
                x.removed(curIs)
              }
            case PathwayAna.nameStr =>
              modify(_: FsData)(_.pathwayAna).using { x =>
                x.removed(curIs)
              }
            case Union.nameStr =>
              modify(_: FsData)(_.union).using { x =>
                x.removed(curIs)
              }
            case Intersect.nameStr =>
              modify(_: FsData)(_.intersect).using { x =>
                x.removed(curIs)
              }
            case Cor.nameStr =>
              modify(_: FsData)(_.cor).using { x =>
                x.removed(curIs)
              }
            case Boruta.nameStr =>
              modify(_: FsData)(_.boruta).using { x =>
                x.removed(curIs)
              }
            case Svm.nameStr =>
              modify(_: FsData)(_.svm).using { x =>
                x.removed(curIs)
              }
            case Rf.nameStr =>
              modify(_: FsData)(_.rf).using { x =>
                x.removed(curIs)
              }
            case Plsda.nameStr =>
              modify(_: FsData)(_.plsda).using { x =>
                x.removed(curIs)
              }
            case Oplsda.nameStr =>
              modify(_: FsData)(_.oplsda).using { x =>
                x.removed(curIs)
              }
            case UniAna.nameStr =>
              modify(_: FsData)(_.uniAna).using { x =>
                x.removed(curIs)
              }
          }
          loop((modifyF andThen newModifyF), xs)
        case Nil => modifyF
      }
    }
    val newFsData = loop(modify(_: FsData)(_.dataMatrix).using(x => x), kindCurIs)
    kindCurIs.foreach { case (kind, curIs) =>
      fieldArrayMap(kind).remove(curIs)
    }
    fsDataChange(newFsData)
  }

}
